﻿/*
Copyright (C) 2014 Dieter Vandroemme


Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace RandomUtils {
    /// <summary>
    /// Helper class for 'serializing / deserializing' strings, objects (gzip and deflate compression included) and structures.
    /// </summary>
    public static class SerializationHelper {
        /// <summary>
        /// Used in the Encode and Decode fx. UTF8 is advised.
        /// </summary>
        public enum TextEncoding {
            /// <summary></summary>
            ASCII = 0,
            /// <summary></summary>
            BigEndianUnicode = 1,
            /// <summary>
            /// Default system encoding, depends on your settings. Use UTF8 instead.
            /// </summary>
            Default = 2,
            /// <summary></summary>
            Unicode = 3,
            /// <summary></summary>
            UTF32 = 4,
            /// <summary></summary>
            UTF7 = 5,
            /// <summary></summary>
            UTF8 = 6
        }
        /// <summary>
        /// Encodes a string to a byte[] using the given encoding.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static byte[] Encode(string text, TextEncoding encoding) {
            switch (encoding) {
                case TextEncoding.ASCII:
                    return Encoding.ASCII.GetBytes(text);
                case TextEncoding.BigEndianUnicode:
                    return Encoding.BigEndianUnicode.GetBytes(text);
                case TextEncoding.Default:
                    return Encoding.Default.GetBytes(text);
                case TextEncoding.Unicode:
                    return Encoding.Unicode.GetBytes(text);
                case TextEncoding.UTF32:
                    return Encoding.UTF32.GetBytes(text);
                case TextEncoding.UTF7:
                    return Encoding.UTF7.GetBytes(text);
                case TextEncoding.UTF8:
                    return Encoding.UTF8.GetBytes(text);
            }
            return null;
        }

        /// <summary>
        /// Decodes a byte[] to a string using the given encoding.
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static string Decode(byte[] arr, TextEncoding encoding) {
            string text = null;
            switch (encoding) {
                case TextEncoding.ASCII:
                    text = Encoding.ASCII.GetString(arr);
                    break;
                case TextEncoding.BigEndianUnicode:
                    text = Encoding.BigEndianUnicode.GetString(arr);
                    break;
                case TextEncoding.Default:
                    text = Encoding.Default.GetString(arr);
                    break;
                case TextEncoding.Unicode:
                    text = Encoding.Unicode.GetString(arr);
                    break;
                case TextEncoding.UTF32:
                    text = Encoding.UTF32.GetString(arr);
                    break;
                case TextEncoding.UTF7:
                    text = Encoding.UTF7.GetString(arr);
                    break;
                case TextEncoding.UTF8:
                    text = Encoding.UTF8.GetString(arr);
                    break;
            }
            return text.Trim('\0');
        }

        /// <summary>
        /// Converts an object to a byte[] uncompressed.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static byte[] Serialize(object obj) {
            byte[] buffer = null;

            //Set the initial buffer size to 1 byte (default == 256 bytes), this way we do not have '\0' bytes in buffer.
            using (var ms = new MemoryStream(1)) {
                var bf = new BinaryFormatter();
                bf.Serialize(ms, obj);

                //.ToArray() was also possible (no '\0' bytes) but this makes a copy of the buffer and results in having twice the buffer in memory.
                buffer = ms.GetBuffer();
                bf = null;
            }
            return buffer;
        }
        /// <summary>
        /// <para>Converts an object to a byte[] using deflate compression.</para>
        /// <para>Use gzip only for text, since it is optimized for that stuff according to wikipedia. Use deflate for everything else.</para>
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="compressionLevel"></param>
        /// <returns></returns>
        public static byte[] SerializeDeflate(object obj, CompressionLevel compressionLevel = CompressionLevel.Optimal) {
            byte[] buffer = null;

            //Set the initial buffer size to 1 byte (default == 256 bytes), this way we do not have '\0' bytes in buffer.
            using (var ms = new MemoryStream(1)) {
                using (var dfStream = new DeflateStream(ms, compressionLevel)) {
                    var bf = new BinaryFormatter();
                    bf.Serialize(dfStream, obj);

                    //.ToArray() was also possible (no '\0' bytes) but this makes a copy of the buffer and results in having twice the buffer in memory.
                    buffer = ms.GetBuffer();
                    bf = null;
                }
            }
            return buffer;
        }
        /// <summary>
        /// <para>Converts an object to a byte[] using gzip compression.</para>
        /// <para>Use gzip only for text, since it is optimized for that stuff according to wikipedia. Use deflate for everything else.</para>
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="compressionLevel"></param>
        /// <returns></returns>
        public static byte[] SerializeGZip(object obj, CompressionLevel compressionLevel = CompressionLevel.Optimal) {
            byte[] buffer = null;

            //Set the initial buffer size to 1 byte (default == 256 bytes), this way we do not have '\0' bytes in buffer.
            using (var ms = new MemoryStream(1)) {
                using (var dfStream = new GZipStream(ms, compressionLevel)) {
                    var bf = new BinaryFormatter();
                    bf.Serialize(dfStream, obj);

                    //.ToArray() was also possible (no '\0' bytes) but this makes a copy of the buffer and results in having twice the buffer in memory.
                    buffer = ms.GetBuffer();
                    bf = null;
                }
            }
            return buffer;
        }

        /// <summary>
        /// <para>Converts a byte[] to an object.</para>
        /// <para>Throws an exception if the array could not be deserialized or the deserialized object could not be cast to the given type.</para>
        /// </summary>
        /// <param name="arr"></param>
        /// <returns></returns>
        public static T Deserialize<T>(byte[] arr) {
            object obj = null;

            using (var ms = new MemoryStream(arr)) {
                var bf = new BinaryFormatter();
                obj = bf.Deserialize(ms);

                bf = null;
            }
            return (T)obj;
        }
        /// <summary>
        /// <para>Converts a byte[] to an object. Decompressed using deflate.</para>
        /// <para>Throws an exception if the array could not be deserialized or the deserialized object could not be cast to the given type.</para>
        /// </summary>
        /// <param name="arr"></param>
        /// <returns></returns>
        public static T DeserializeDeflate<T>(byte[] arr) {
            object obj = null;

            using (var ms = new MemoryStream(arr)) {
                using (var dfStream = new DeflateStream(ms, CompressionMode.Decompress)) {
                    var bf = new BinaryFormatter();
                    obj = bf.Deserialize(dfStream);

                    bf = null;
                }
            }
            return (T)obj;
        }
        /// <summary>
        /// <para>Converts a byte[] to an object. Decompressed using gzip.</para>
        /// <para>Throws an exception if the array could not be deserialized or the deserialized object could not be cast to the given type.</para>
        /// </summary>
        /// <param name="arr"></param>
        /// <returns></returns>
        public static T DeserializeGzip<T>(byte[] arr) {
            object obj = null;

            using (var ms = new MemoryStream(arr)) {
                using (var dfStream = new GZipStream(ms, CompressionMode.Decompress)) {
                    var bf = new BinaryFormatter();
                    obj = bf.Deserialize(dfStream);

                    bf = null;
                }
            }
            return (T)obj;
        }

        /// <summary>
        /// <para>Handy for stuff from the clr / interop.</para>
        /// <para>Use this only for structs that contains only primitives!</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str"></param>
        /// <returns></returns>
        public static byte[] MarshalStructToBytes<T>(T str) where T : struct {
            int size = Marshal.SizeOf(str);
            byte[] arr = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(str, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);
            Marshal.FreeHGlobal(ptr);
            return arr;
        }
        /// <summary>
        /// <para>Handy for stuff from the clr / interop.</para>
        /// <para>Use this only for structs that contains only primitives!</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="arr"></param>
        /// <returns></returns>
        public static T MarshalBytesToStruct<T>(byte[] arr) where T : struct {
            T str = Activator.CreateInstance<T>();
            int size = Marshal.SizeOf(str);
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.Copy(arr, 0, ptr, size);
            str = (T)Marshal.PtrToStructure(ptr, str.GetType());
            Marshal.FreeHGlobal(ptr);
            return str;
        }

        /// <summary>
        /// Converts a byte array to a hex using the given separator.
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static string BytesToHex(byte[] arr, string separator = "") {
            if (separator == ",") return BitConverter.ToString(arr);
            return BitConverter.ToString(arr).Replace(",", separator);
        }
        /// <summary>
        /// Converts a seperated hex to a byte array using the given seperator.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static byte[] HexToBytes(string s, string separator = "") {
            byte[] arr;
            if (separator.Length == 0) {
                arr = new byte[s.Length / 2];
                for (int i = 0; i < s.Length; i += 2)
                    arr[i / 2] = Convert.ToByte(s.Substring(i, 2), 16);

                return arr;
            }

            string[] split = s.Split(new string[] { separator }, StringSplitOptions.None);
            arr = new byte[split.Length];
            for (int i = 0; i < split.Length; i++)
                arr[i] = Convert.ToByte(split[i], 16);

            return arr;
        }
    }
}
